
/**
 * 继承是实现类复用的重要手段，但继承带来了一个最大的坏处：破坏封装。
 * 相比之下，组合也是实现类复用的重要方式，而采用组合方式来实现类复用则能提供更好的封装性。
 * <p>
 * 通过继承，子类破坏了父类的封装，子类可以直接访问父类的成员变量（内部信息）和方法，从而造成子类和父类的严重耦合。
 * <p>
 * 为了保证父类有良好的封装性，不会被子类随意改变，设计父类通常应该遵循如下规则。
 * ➢ 尽量隐藏父类的内部数据。尽量把父类的所有成员变量都设置成private访问类型，不要让子类直接访问父类的成员变量。
 * ➢ 不要让子类可以随意访问、修改父类的方法。父类中那些仅为辅助其他的工具方法，应该使用private访问控制符修饰，让子类无法访问该方法；
 * 如果父类中的方法需要被外部类调用，则必须以public修饰，但又不希望子类重写该方法，可以使用final修饰符（该修饰符后面会有更详细的介绍）来修饰该方法；
 * 如果希望父类的某个方法被子类重写，但不希望被其他类自由访问，则可以使用protected来修饰该方法。
 * ➢ 尽量不要在父类构造器中调用将要被子类重写的方法。
 */
class Base {
    public Base() {
//        test();     // 将会调用子类的test方法，此时Sub对象的name实例变量是null，因此将引发空指针异常。
    }

    public void test() {
        System.out.println("将被子类重写的方法");
    }
}

class Sub extends Base {
    private String name;

    public void test() {
        System.out.println("子类重写父类的方法，其name字段长度为" + name.length());
    }

    public static void main() {
        new Sub();
    }
}

/**
 * 如果想把某些类设置成最终类，即不能被当成父类，则可以使用final修饰这个类，
 * 例如JDK提供的java.lang.String类和java.lang.System类。
 * 除此之外，使用private修饰这个类的所有构造器，从而保证子类无法调用该类的构造器，也就无法继承该类。
 * 对于把所有的构造器都使用private修饰的父类而言，可另外提供一个静态方法，用于创建该类的实例。
 * <p>
 * 到底何时需要从父类派生新的子类呢？不仅需要保证子类是一种特殊的父类，而且需要具备以下两个条件之一。
 * ➢ 子类需要额外增加成员变量，而不仅仅是变量值的改变。
 * 例如从Person类派生出Student子类，Person类里没有提供grade（年级）成员变量，
 * 而Student类需要grade成员变量来保存Student对象就读的年级，这种父类到子类的派生，就符合Java继承的前提。
 * ➢ 子类需要增加自己独有的行为方式（包括增加新的方法或重写父类的方法）。
 * 例如从Person类派生出Teacher类，其中Teacher类需要增加一个teaching()方法，该方法用于描述Teacher对象独有的行为方式：教学。
 * <p>
 * 上面详细介绍了继承关系可能存在的问题，以及如何处理这些问题。如果只是出于类复用的目的，并不一定需要使用继承，完全可以使用组合来实现。
 */

/**
 * 利用组合实现复用
 */
class Animal {
    private void beat() {
        System.out.println("心脏跳动...");
    }

    public void breathe() {
        beat();
        System.out.println("吸一口气，吐一口气，呼吸中...");
    }
}

class Bird {
    // 将原来的父类组合到原来的子类，作为子类的一个组合部分
    private Animal a;

    public Bird(Animal a) {
        this.a = a;
    }

    // 重新定义一个自己的breathe方法
    public void breathe() {
        a.breathe();
    }

    public void fly() {
        System.out.println("我在天空自在的飞翔...");
    }
}

class CompositeTest {
    public static void main() {
        // 此时需要显式创建被组合的对象
        Bird b = new Bird(new Animal());
        b.breathe();
        b.fly();
    }
}

/**
 * 大部分时候，继承关系中从多个子类里抽象出共有父类的过程，类似于组合关系中从多个整体类里提取被组合类的过程；
 * 继承关系中从父类派生子类的过程，则类似于组合关系中把被组合类组合到整体类的过程。
 * <p>
 * 到底该用继承？还是该用组合呢？继承是对已有的类做一番改造，以此获得一个特殊的版本。简而言之，就是将一个较为抽象的类改造成能适用于某些特定需求的类。
 * 因此，对于上面的Bird和Animal的关系，使用继承更能表达其现实意义。用一个动物来合成一只鸟毫无意义：鸟并不是由动物组成的。
 * 反之，如果两个类之间有明确的整体、部分的关系，例如Person类需要复用Arm类的方法（Person对象由Arm对象组合而成），
 * 此时就应该采用组合关系来实现复用，把Arm作为Person类的组合成员变量，借助于Arm的方法来实现Person的方法，这是一个不错的选择。
 * <p>
 * 总之，继承要表达的是一种“是（is-a）”的关系，而组合表达的是“有（has-a）”的关系。
 */

public class Composite {
    public static void main(String[] args) {
        Sub.main();
        CompositeTest.main();
    }
}
